package com.hzlj.position.locate.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.fz.common.base.dto.PageParamDTO;
import com.fz.common.base.dto.QueryPageResultDTO;
import com.fz.common.dict.annotation.DictConvert;
import com.fz.common.redis.core.RedisConfig;
import com.fz.common.redis.util.RedisGeoUtil;
import com.google.common.collect.Maps;
import com.hzlj.common.message.common.dto.message.MessageNoticeUserDTO;
import com.hzlj.common.message.common.util.MessageRestUtil;
import com.hzlj.position.config.config.PositionConfig;
import com.hzlj.position.config.service.NoticeConfigService;
import com.hzlj.position.locate.common.dto.abnGathering.AbnGatheringCacheDTO;
import com.hzlj.position.locate.common.dto.abnGathering.AbnGatheringQueryParamDTO;
import com.hzlj.position.locate.common.dto.abnGathering.AbnGatheringQueryResultDTO;
import com.hzlj.position.locate.common.dto.abnGatheringJzry.AbnGatheringJzryQueryParamDTO;
import com.hzlj.position.locate.common.dto.abnGatheringJzry.AbnGatheringJzryQueryResultDTO;
import com.hzlj.position.locate.common.model.AbnGathering;
import com.hzlj.position.locate.common.model.Position;
import com.hzlj.position.locate.dao.AbnGatheringDao;
import com.hzlj.position.locate.service.AbnGatheringJzryService;
import com.hzlj.position.locate.service.AbnGatheringService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static com.hzlj.position.config.config.IdConfig.POSITION_ID_GEN;

/**
 * 定位分析-聚众告警表(AbnGathering)表服务接口
 *
 * @author lifh
 * @date 2023-03-28 10:20:22
 */
@Slf4j
@Service
public class AbnGatheringServiceImpl implements AbnGatheringService {
    private final static String GEO_KEY = "position:gathering:%d";
    private final static Jackson2JsonRedisSerializer<Object> keySerializer = RedisConfig.valueSerializer();
    /**
     * 多少毫秒一个格子
     */
    private final static Integer KEY_MILLI_SECONDS_PER_UNIT = 5 * 60 * 1000;
    /**
     * 每个格子保存多少秒
     */
    private final static Integer SECONDS_PER_UNIT = (5 * KEY_MILLI_SECONDS_PER_UNIT) / 1000;
    /**
     * 多少距离之内
     */
    private final static int CHECK_METERS = 20;
    @Resource
    private PositionConfig positionConfig;

    @Resource
    private AbnGatheringDao abnGatheringDao;
    @Resource
    private AbnGatheringJzryService abnGatheringJzryService;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private NoticeConfigService noticeConfigService;


    @Override
    public void syncPositions(List<Position> positions) {
        if (!positionConfig.getAbn().getGathering()) {
            return;
        }
        Map<String, List<Position>> values = positions.stream()
                .collect(Collectors.groupingBy(e -> String.format(GEO_KEY, e.getPositionTime().getTime() / KEY_MILLI_SECONDS_PER_UNIT)));

        //保存
        for (Map.Entry<String, List<Position>> entry : values.entrySet()) {
            try {
                List<RedisGeoUtil.GeoBody> bodies = entry.getValue().stream()
                        .map(e -> new RedisGeoUtil.GeoBody(
                                BeanUtil.copyProperties(e, AbnGatheringCacheDTO.class).setPositionId(e.getId()),
                                Double.parseDouble(e.getLongitude()),
                                Double.parseDouble(e.getLatitude())))
                        .collect(Collectors.toList());
                RedisGeoUtil.addGeos(redisTemplate, entry.getKey(), bodies, SECONDS_PER_UNIT);
            } catch (Exception e) {
                log.warn("同步定位记录到redis失败", e);
            }
        }

        //分析聚众信息
        abnGathering(positions);
    }

    private void abnGathering(List<Position> positions) {
        //5个时间格子的数据
        int periods = (int) (new Date().getTime() / KEY_MILLI_SECONDS_PER_UNIT);
        List<String> keys = IntStream.rangeClosed(0, 4).boxed()
                .map(e -> String.format(GEO_KEY, periods - e))
                .collect(Collectors.toList());
        for (Position position : positions) {
            try {
                List<GeoResults<RedisGeoCommands.GeoLocation<Object>>> nears
                        = RedisGeoUtil.nears(redisTemplate, keys, CHECK_METERS, Double.parseDouble(position.getLongitude()), Double.parseDouble(position.getLatitude()), 0);

                //得到有效记录
                List<AbnGatheringCacheDTO> filterKeys = nears.stream().flatMap(e -> e.getContent().stream())
                        .filter(e -> ObjectUtil.isNotEmpty(e.getContent()) && ObjectUtil.isNotEmpty(e.getContent().getName()))
                        .map(e -> (AbnGatheringCacheDTO) keySerializer.deserialize((byte[]) e.getContent().getName()))
                        .filter(Objects::nonNull)
                        .sorted(Comparator.comparing(AbnGatheringCacheDTO::getPositionTime))
                        .collect(Collectors.toList());

                //人数
                long personNumber = filterKeys.stream().map(AbnGatheringCacheDTO::getJzId).distinct().count();
                if (personNumber < positionConfig.getAbn().getMinGatheringPersonNum()) {
                    return;
                }
                AbnGatheringCacheDTO first = filterKeys.get(0);
                AbnGatheringCacheDTO end = filterKeys.get(filterKeys.size() - 1);

                String positionId = first.getPositionId();
                Date startTime = first.getPositionTime();
                Date endTime = end.getPositionTime();

                String gatheringId = this.abnGatheringDao.getByMinPositionId(positionId);
                //新增
                if (gatheringId == null) {
                    gatheringId = add(first, startTime, endTime, (int) personNumber);
                } else {
                    update(gatheringId, startTime, endTime, (int) personNumber);
                }
                //新增位置信息，需要去掉重复的
                boolean taf = abnGatheringJzryService.save(gatheringId, filterKeys);
                //修改同案犯标记
                this.abnGatheringDao.updateTaf(gatheringId, taf);
                //发送通知
                this.noticeGzry(gatheringId);
            } catch (Exception e) {
                log.error("聚众分析错误:position={}", position, e);
            }
        }
    }


    private String add(AbnGatheringCacheDTO key, Date startTime, Date endTime, int personNum) {
        AbnGathering gathering = BeanUtil.copyProperties(key, AbnGathering.class);
        gathering.setId(POSITION_ID_GEN.nextIdStr());
        gathering.setMinPositionId(key.getPositionId());
        gathering.setPeopleNum(personNum);
        gathering.setStartTime(startTime);
        gathering.setEndTime(endTime);
        gathering.setCreateTime(new Date());
        this.abnGatheringDao.save(gathering);
        return gathering.getId();
    }


    private void update(String gatheringId, Date startTime, Date endTime, int personNum) {
        AbnGathering gathering = new AbnGathering();
        gathering.setId(gatheringId);
        gathering.setStartTime(startTime);
        gathering.setEndTime(endTime);
        gathering.setPeopleNum(personNum);
        this.abnGatheringDao.updateById(gathering);
    }

    /**
     * 查询列表：分页
     *
     * @param dto  查询条件
     * @param page 分页信息
     * @return 对象列表
     */
    @Override
    @DictConvert
    public QueryPageResultDTO<AbnGatheringQueryResultDTO> pageAbnGathering(AbnGatheringQueryParamDTO dto, PageParamDTO page) {
        return this.abnGatheringDao.pageAbnGathering(dto, page);
    }

    private void noticeGzry(String gatheringId) {
        List<AbnGatheringJzryQueryResultDTO> jzries
                = this.abnGatheringJzryService.listAbnGatheringJzry(new AbnGatheringJzryQueryParamDTO().setGatheringId(gatheringId));
        //矫正人员姓名
        String xms = jzries.stream().map(AbnGatheringJzryQueryResultDTO::getXm).collect(Collectors.joining(","));
        //按照jgCode分组
        Map<String, List<AbnGatheringJzryQueryResultDTO>> jzreisByJgCode = jzries.stream().collect(Collectors.groupingBy(AbnGatheringJzryQueryResultDTO::getJgCode));
        jzreisByJgCode.forEach((k, v) -> {
            Map<String, Object> params = Maps.newHashMap();
            params.put("jzryXm", xms);
            params.put("abnTypeText", "多人聚集");
            params.put("positionTime", DateUtil.formatDateTime(v.get(0).getPositionTime()));
            MessageRestUtil.noticeQueryUsers(
                    "abn-gathering-gzry",
                    gatheringId,
                    params,
                    MessageNoticeUserDTO.of(true, k, noticeConfigService.defaultMessageNoticeAuths(k))
            );
        });
    }
}
